﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CxExtension;
using ExcelDataReader;

namespace ExcelExporter
{

	public interface ILuaAnnation
	{
		string ToAnnation();
	}


	public class LuaGenerater
	{
		public int cfgline = 0;
		public int typeline = 1;
		public int fieldline = 2;
		public int serviceFieldLine = 3;//
		public int descline = 4;//
		public int startPos = 5;
		public string outpath;//
		public string inpath;//
		public string ext = ".lua";//
		public string tableIgnorePostfix = "";//

		public string curFile;
		public string curFileName;//
		private readonly CultureInfo cinfo = Thread.CurrentThread.CurrentCulture;
		private readonly StringBuilder tableBuilder = new StringBuilder(1000);
		private readonly StringBuilder tableBuilder2 = new StringBuilder(1000);
		private List<ClassInfo> clsInfos = new List<ClassInfo>();

		private const string InitialNewLine = "\r\n";
		private const string tabSp = "\t";

		public LuaGenerater()
		{
			cfgline = Properties.Settings.Default.cfgline;
			typeline = Properties.Settings.Default.clientTypeLine;
			fieldline = Properties.Settings.Default.fieldLine;
			serviceFieldLine = Properties.Settings.Default.serviceTypeLine;
			descline = Properties.Settings.Default.descLine;
			startPos = Properties.Settings.Default.startPos;
			tableIgnorePostfix = Properties.Settings.Default.tableIgnorePostfix;
		}
		public string LuaAnnotationPath()
		{
			return Path.Combine(outpath, "LuaAnnation.lua");
		}
		public string ChangeListPath(string path)
		{
			return Path.Combine(path, "ChangeList.ini");
		}

		public void GenerateDir(string dir)
		{
			inpath = dir;
			var luaan = LuaAnnotationPath();
			if (File.Exists(luaan))
			{
				File.Delete(luaan);
			}
			var files = Directory.GetFiles(dir, "*.xls", SearchOption.AllDirectories);
			//var file = STRING;
			var oldMap = ReadFileListMd(dir);
			var newMap = GetFileListMd(dir);
			var changeMap = GetFileListMd(newMap, oldMap);
			if (changeMap.Count > 0)
			{



			}
			foreach (var it in files)
			{
				if (it.EndsWith(".xls") == false)
				{
					continue;
				}
				var ignoreData = changeMap.ContainsKey(it) == false;
				GenerateFile(it, ignoreData);
			}
			if (ErrorFlag.hasError == false)
			{
				WriteFileRecord(dir, newMap);
			}
			GenerateLuaAnnation(LuaAnnotationPath());
			GenerateLuaRequires(Path.Combine(outpath, "LuaRequires.lua"));
		}

		public Dictionary<string, string> GetFileListMd(Dictionary<string, string> newMap, Dictionary<string, string> cueMap)
		{
			var dir = new Dictionary<string, string>();
			foreach (var item in newMap)
			{
				string value;
				var changed = false;
				if (cueMap.TryGetValue(item.Key, out value))
				{
					changed = string.Equals(value, item.Value) == false;
				}
				else
				{
					changed = true;
				}

				if (changed)
				{
					dir[item.Key] = item.Value;
				}
			}

			return dir;
		}

		public Dictionary<string, string> GetFileListMd(string dir)
		{
			var files = Directory.GetFiles(dir, "*.xls", SearchOption.AllDirectories);
			var direct = new Dictionary<string, string>();
			foreach (var file in files)
			{
				var info = new FileInfo(file);
				direct[file] = info.LastWriteTime.ToLongTimeString();
			}

			return direct;
		}

		public void WriteFileRecord(string dir, Dictionary<string, string> recordmap)
		{
			//var file = File.AppendAllText();
			var bld = new StringBuilder(1000);
			using (StreamWriter streamWriter = new StreamWriter(ChangeListPath(dir), false, Encoding.UTF8, 1024))
			{
				foreach (var it in recordmap)
				{

					bld.AppendFormat("{0}{1}{2}", it.Key, tabSp, it.Value);
					bld.AppendLine();
					streamWriter.Write(bld);
					bld.Clear();
				};
			}
		}
		public Dictionary<string, string> ReadFileListMd(string dir)
		{
			var direct = new Dictionary<string, string>();
			var finfo = new FileInfo(dir);
			var filepath = ChangeListPath(dir);
			if (File.Exists(filepath))
			{
				var fileStr = File.ReadAllText(filepath);
				var list = fileStr.Split(InitialNewLine);
				foreach (var s in list)
				{
					if (s.IsNullOrEmpty())
					{
						continue;
					}
					var cfg = s.Split(tabSp);
					direct[cfg[0]] = cfg[1];
				}
			}

			return direct;
		}
		public void GenerateFile(string path, bool ignoreData = false)
		{

#if LogPath

			Console.WriteLine("exprot file:{0}", path);
#endif
			curFile = path;
			curFileName = Path.GetFileName(path);
			using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
			{

				// Auto-detect format, supports:
				//  - Binary Excel files (2.0-2003 format; *.xls)
				//  - OpenXml Excel files (2007 format; *.xlsx)
				using (var reader = ExcelReaderFactory.CreateReader(stream))
				{
					var result = reader.AsDataSet();

					//result.Tables.
					// 2. Use the AsDataSet extension method
					foreach (DataTable it in result.Tables)
					{
#if true1
						try
						{
						
#endif
						if (it.TableName.IsAllLetter() == false)
						{
							continue;
						}
						var clsinfo = GenerateTable(it, outpath, ignoreData);
						if (clsinfo != null)
						{
							clsInfos.Add(clsinfo);
						}
#if true1
						}

			
						catch (Exception e)
						{
							Console.WriteLine("{0}\n 子表 :{1} 导出错误:{3}", curFile,it.TableName,e);
							ErrorFlag.Add();
							continue;
							// ignored
						}
#endif
						//var it = result.Tables[0];
					}
					//it.Rows[0]

					// The result of each spreadsheet is in result.Tables
					//result.Tables.
				}
			}
		}

		public void GenerateLuaAnnation(string path)
		{
			var luas = File.AppendText(path);
			foreach (var it in clsInfos)
			{
				luas.WriteLine(it.ToAnnation());
			}
			luas.Flush();
			luas.Close();
		}

		public void GenerateLuaRequires(string path)
		{
			if (File.Exists(path))
			{
				File.Delete(path);
			}
			var luas = File.AppendText(path);
			luas.WriteLine("---@class ExcelConfig");
			foreach (var it in clsInfos)
			{
				luas.WriteLine("---@field public {0} {2} @{1}", it.clsname, it.desc, it.clstype);
			}
			luas.WriteLine("local c = {}");
			foreach (var it in clsInfos)
			{
				luas.WriteLine(it.ToLuaRequire());
			}
			luas.WriteLine("return c");
			luas.Flush();
			luas.Close();
		}
		public ClassInfo GenerateTable(DataTable table, string outpath, bool ignoreData = false)
		{

#if LogPath
			Console.WriteLine(" -export table{0}", table.TableName);

#endif
			var tableName = table.TableName.Trim();
			if (tableIgnorePostfix.HasValue() && tableName.EndsWith(tableIgnorePostfix))
			{
				tableName = tableName.Replace(tableIgnorePostfix, "");
			}

			tableName = tableName.ToFirstUpper();
			var clsinfo = new ClassInfo
			{
				clsname = tableName,
				srcFile = curFileName,

			};
			var rows = table.Rows;

			var cfglist = rows[cfgline].ItemArray.ToStringList();
			var typelist = rows[typeline].ItemArray.ToStringList();
			var fieldlist = rows[fieldline].ItemArray.ToStringList();
			var serverfieldlist = rows[serviceFieldLine].ItemArray.ToStringList();
			var desclist = rows[descline].ItemArray.ToStringList();
			var keyLienStr = "a";
			var splitCfg = "";
			var enumCfg = "";
			var ignoreRowCfg = "";
			var ignoreExport = "";
			//cfgline:
			//	a         b    c		d		e		f			g
			//classname,desc,keyline,splitcfg,enumCfg,ignoreRowCfg,ignoreExport
			if (cfglist.Count > 6)
			{
				var str = cfglist[6];
				if (string.IsNullOrWhiteSpace(str) == false)
				{
					return null;

				}
			}

			if (cfglist.Count >= 2)
			{
				var clsnm = cfglist[0];
				clsinfo.desc = cfglist[1];
				clsinfo.clsname = string.IsNullOrWhiteSpace(clsnm) ? tableName : clsnm;
			}

			if (cfglist.Count >= 3)
			{
				var str = cfglist[2];
				if (!string.IsNullOrWhiteSpace(str))
				{
					keyLienStr = str;
				}
			}

			if (cfglist.Count > 3)
			{
				var str = cfglist[3];
				if (!string.IsNullOrWhiteSpace(str))
				{
					splitCfg = str;
				}
			}

			if (cfglist.Count > 4)
			{
				var str = cfglist[4];
				if (!string.IsNullOrWhiteSpace(str))
				{
					enumCfg = str;
				}
			}
			if (cfglist.Count > 5)
			{
				var str = cfglist[5];
				if (!string.IsNullOrWhiteSpace(str))
				{
					ignoreRowCfg = str;

				}
			}

			{
				var strs = splitCfg.Split(',');
			}

			var keyLine = (int)Program.LetterToIndex(keyLienStr);
			if (keyLine >= cfglist.Count && keyLine > 100)
			{
				Console.WriteLine("配置表:file:{0} sheet:{1},填写keyline填写错误,不能填写为:{2},强制设置为默认值0", curFileName, tableName, keyLienStr);
				ErrorFlag.Add();
				keyLine = 0;
			}

			var enumLine = enumCfg.IsNullOrEmpty() ? -1 : Program.LetterToIndex(enumCfg);
			if (enumLine >= 0)
			{
				if (enumLine >= cfglist.Count && enumLine > 100)
				{
					Console.WriteLine("配置表:file:{0} sheet:{1},填写enumCfg填写错误,不能填写为:{2},强制设置为默认值0", curFileName, tableName, enumCfg);
					ErrorFlag.Add();
					enumLine = -1;
				}
			}

			var ignoreRowMap = new HashSet<int>();
			var descRowInfo = fieldlist.FindIndex((s => s.ToLower() == "desc"));
			if (descRowInfo != -1)
			{
				ignoreRowMap.Add(descRowInfo);
			}

			if (ignoreRowCfg.HasValue())
			{
				if (ignoreRowCfg.Equals("0"))
				{
					ignoreRowMap.Clear();
				}
				else
				{
					var strs = ignoreRowCfg.Split(',');
					foreach (var it in strs)
					{
						var va = it.LetterToIndex();
						ignoreRowMap.Add((int)va);
					}
				}
			}



			for (var i = 0; i < fieldlist.Count; i++)
			{
				var it = fieldlist[i];
				if (it.IsNullOrEmpty())
				{
					continue;
				}
				var tp = string.IsNullOrWhiteSpace(typelist[i]) ? serverfieldlist[i] : typelist[i];

				clsinfo.Infos.Add(new FieldInfo()
				{
					name = it,
					idx = i,
					type = tp.ToLuaType(),
					desc = desclist[i]
				});
			}
			clsinfo.Infos.RemoveAll((s => ignoreRowMap.Contains(s.idx)));
			if (ignoreData)
			{
				return clsinfo;
			}

			var keyLineFielInfo = clsinfo.Infos[keyLine];

			//export table
			var file = Path.Combine(outpath, tableName + ext);
			var stream = File.CreateText(file);

			var bld = tableBuilder;
			var bld2 = tableBuilder2;
			bld.Clear();
			stream.WriteLine("---parse form:{0} key:{1}", curFileName, fieldlist[(int)keyLine]);
			stream.WriteLine("---@type {0}", clsinfo.clstype);
			stream.WriteLine("local c = {");


			for (var i = startPos; i < rows.Count; i++)
			{
				var hasValue = false;
				bld2.Clear();
				var row = rows[i];
				var luatablKeyFormatStr = keyLineFielInfo.type == "string" ? "{0}" : "[{0}]";
				var luatablKeyStr = string.Format(luatablKeyFormatStr, row.ItemArray[keyLine]);
				bld2.AppendFormat("\t{0} = {{", luatablKeyStr);
				var isBreak = false;
				for (var j = 0; j < clsinfo.Infos.Count; j++)
				{
					var info = clsinfo.Infos[j];
					var v = row.ItemArray[info.idx].ToString();
					if (info.idx == 0 && v.IsNullOrEmpty())
					{
						isBreak = true;
						break;
					}

					if (v.IsNullOrEmpty())
					{
						continue;
					}

					if (info.idx != keyLine)
					{
						hasValue = true;
					}

					v = v.Replace("\n", "");
					v = v.Replace("\"", "");
					bld2.Append(info.ToLuaDefine(v));
				}

				if (isBreak || hasValue == false)
				{
					continue;
				}

				bld2.Remove(bld2.Length - 1, 1);
				bld2.Append(" },\n");
				bld.Append(bld2);
			}
			if (rows.Count > startPos && bld.Length > 0)
				bld.Remove(bld.Length - 1, 1);

			stream.Write(bld);
			stream.WriteLine("\n}");
			stream.WriteLine("return c");
			stream.Flush();
			stream.Close();

			//export enum
			if (enumLine >= 0)
			{

				var enumLineFielInfo = clsinfo.Infos.Find(it => it.idx == enumLine);
				var enumName = string.Format("{0}Enum_{1}", clsinfo.clsname, enumLineFielInfo.name); ;
				var enumFileName = Path.Combine(outpath, enumName + ext); ;
				stream = File.CreateText(enumFileName);
				var enumHashSet = new HashSet<string>();
				//bld = tableBuilder;
				bld.Clear();
				stream.WriteLine("---parse form:{0} key:{1}", curFileName, fieldlist[(int)enumLine]);
				stream.WriteLine("---@class {0}", enumName);
				stream.WriteLine("local c = {");
				for (var i = startPos; i < rows.Count; i++)
				{
					var row = rows[i];
					var key = row.ItemArray[enumLine].ToString();
					if (enumHashSet.Add(key))
					{
						var enumDef = string.Format("\t{0} = \'{0}\',", key);
						bld.AppendLine(enumDef);
					}
					else
					{
						Console.WriteLine("配置表:file:{0} sheet:{1},拥有重复的枚举key:{2},强制设置为默认值0", curFileName, tableName, key);
						ErrorFlag.Add();
					}

				}

				if (rows.Count > startPos)
				{
					bld.Remove(bld.Length - 1, 1);
				}

				stream.Write(bld);
				stream.WriteLine("}");
				stream.WriteLine("return c");
				stream.Flush();
				stream.Close();
			}

			return clsinfo;
		}
	}
}
